﻿#pragma once

#include <cstdint>
#include <functional>
#include <list>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <type_traits>
#include <typeinfo>

namespace kratos {
namespace service {

/**
 * 内存分配器.
 *
 * 保证在玩家组件内分配的内存在组件卸载时都会被释放
 */
class MemoryAllocator {
private:
  /**
   * std::shared_ptr自定义数组删除器，只支持trival和standard_layout对象.
   *
   * \param p 需要销毁的数组地址
   */
  template <typename T> void ptr_raw_deleter(T *p) {
    if (!p) {
      return;
    }
    dealloc(p);
  }
  /**
   * std::shared_ptr对象删除器
   *
   * \param obj
   */
  template <typename T> void ptr_deleter(T *obj) {
    if (!obj) {
      return;
    }
    obj->~T();
    dealloc(obj);
  }
  /**
   * std::unique_ptr对象删除器
   */
  template <typename T> class UniquePtrDeleter {
    MemoryAllocator *allocator_{nullptr}; ///< 内存分配器

  public:
    UniquePtrDeleter(MemoryAllocator *allocator) : allocator_(allocator) {}
    void operator()(T *obj) {
      if (!obj) {
        return;
      }
      obj->~T();
      allocator_->dealloc(obj);
    }
  };
  /**
   * std::unique_ptr数组删除器，只支持trival和standard_layout对象.
   */
  template <typename T> class UniqueRawDeleter {
    MemoryAllocator *allocator_{nullptr}; ///< 内存分配器

  public:
    UniqueRawDeleter(MemoryAllocator *allocator) : allocator_(allocator) {}
    void operator()(T *p) { allocator_->dealloc(p); }
  };

public:
  template <typename T>
  using unique_memory_ptr = std::unique_ptr<T, UniquePtrDeleter<T>>;

  template <typename T>
  using unique_memory_raw_ptr = std::unique_ptr<T[], UniqueRawDeleter<T>>;

public:
  virtual ~MemoryAllocator() {}
  /**
   * 输出分配器调试信息.
   *
   * \param os
   * \return
   */
  virtual auto dump(std::ostream &os) -> void = 0;
  /**
   * 做一次内存快照.
   *
   * \return
   */
  virtual auto snapshot() -> void = 0;
  /**
   * 分配对象
   *
   * \param args 构造参数
   * \return 对象的共享指针
   */
  template <typename T, typename... ArgsT>
  auto make_shared(ArgsT... args) -> std::shared_ptr<T> {
    auto pointer = alloc(sizeof(T));
    return std::shared_ptr<T>(new (pointer) T(std::forward<ArgsT>(args)...),
                              std::bind(&MemoryAllocator::ptr_deleter<T>, this,
                                        std::placeholders::_1));
  }
  /**
   * 分配对象
   *
   * \param args 构造参数
   * \param file_name 文件名
   * \param line 行号
   * \return 对象的共享指针
   */
  template <typename T, typename... ArgsT>
  auto make_shared_debug(const char* file_name, int line, ArgsT... args)
      -> std::shared_ptr<T> {
    auto pointer = alloc(sizeof(T), typeid(T).hash_code(), typeid(T).name(),
                         file_name, line);
    return std::shared_ptr<T>(new (pointer) T(std::forward<ArgsT>(args)...),
                              std::bind(&MemoryAllocator::ptr_deleter<T>, this,
                                        std::placeholders::_1));
  }
  /**
   * 分配数组，只能分配trival和standard_layout类型.
   *
   * \param count 数组元素数量
   * \return 数组共享指针
   */
  template <typename T>
  auto make_shared_array(std::size_t count) -> std::shared_ptr<T[]> {
    if (!std::is_standard_layout<T>::value && !std::is_trivial<T>::value) {
      return std::shared_ptr<T[]>();
    }
    return std::shared_ptr<T[]>(reinterpret_cast<T *>(alloc(count * sizeof(T))),
                                std::bind(&MemoryAllocator::ptr_raw_deleter<T>,
                                          this, std::placeholders::_1));
  }
  /**
   * 分配数组，只能分配trival和standard_layout类型.
   *
   * \param count 数组元素数量
   * \param file_name 文件名
   * \param line 行号
   * \return 数组共享指针
   */
  template <typename T>
  auto make_shared_array_debug(std::size_t count, const char *file_name = __FILE__,
                               int line = __LINE__) -> std::shared_ptr<T[]> {
    if (!std::is_standard_layout<T>::value && !std::is_trivial<T>::value) {
      return std::shared_ptr<T[]>();
    }
    return std::shared_ptr<T[]>(
        reinterpret_cast<T *>(alloc(count * sizeof(T), typeid(T).hash_code(),
                                    typeid(T).name(), file_name, line)),
        std::bind(&MemoryAllocator::ptr_raw_deleter<T>, this,
                  std::placeholders::_1));
  }
  /**
   * 分配对象
   *
   * \param args 构造参数
   * \return 对象的唯一指针
   */
  template <typename T, typename... ArgsT>
  auto make_unique(ArgsT... args) -> unique_memory_ptr<T> {
    auto pointer = alloc(sizeof(T));
    auto *ptr = new (pointer) T(std::forward<ArgsT>(args)...);
    return unique_memory_ptr<T>(ptr, this);
  }
  /**
   * 分配对象
   *
   * \param args 构造参数
   * \param file_name 文件名
   * \param line 行号
   * \return 对象的唯一指针
   */
  template <typename T, typename... ArgsT>
  auto make_unique_debug(const char* file_name, int line, ArgsT... args)
      -> unique_memory_ptr<T> {
    auto pointer = alloc(sizeof(T), typeid(T).hash_code(), typeid(T).name(),
                         file_name, line);
    auto *ptr = new (pointer) T(std::forward<ArgsT>(args)...);
    return unique_memory_ptr<T>(ptr, this);
  }
  /**
   * 分配数组，只能分配trival和standard_layout类型.
   *
   * \param count 数组元素数量
   * \return 数组唯一指针
   */
  template <typename T>
  auto make_unique_array(std::size_t count) -> unique_memory_raw_ptr<T> {
    if (!std::is_standard_layout<T>::value && !std::is_trivial<T>::value) {
      return unique_memory_raw_ptr<T>(nullptr, nullptr);
    }
    auto ptr = reinterpret_cast<T *>(alloc(count * sizeof(T)));
    return unique_memory_raw_ptr<T>(ptr, this);
  }
  /**
   * 分配数组，只能分配trival和standard_layout类型.
   *
   * \param count 数组元素数量
   * \param file_name 文件名
   * \param line 行号
   * \return 数组唯一指针
   */
  template <typename T>
  auto make_unique_array_debug(std::size_t count, const char *file_name = __FILE__,
                               int line = __LINE__) -> unique_memory_raw_ptr<T> {
    if (!std::is_standard_layout<T>::value && !std::is_trivial<T>::value) {
      return unique_memory_raw_ptr<T>(nullptr, nullptr);
    }
    auto ptr =
        reinterpret_cast<T *>(alloc(count * sizeof(T), typeid(T).hash_code(),
                                    typeid(T).name(), file_name, line));
    return unique_memory_raw_ptr<T>(ptr, this);
  }

private:
  /**
   *  容器提供的内存分配方法.
   *
   * \param size 需要分配的长度，字节
   * \return 已分配的内存起始地址
   */
  virtual auto alloc(std::size_t size) -> void * = 0;
  /**
   * 容器提供的内存释放方法.
   *
   * \param ptr 需要释放内存的起始地址，由alloc分配
   * \return
   */
  virtual auto dealloc(void *ptr) -> void = 0;
  /**
   *  容器提供的内存分配方法，调试版本
   *
   * \param size 需要分配的长度，字节
   * \param hash_id 名字对应的hash_id
   * \param name 类型名
   * \param file_name 分配的文件名
   * \param line 行号
   * \return 已分配的内存起始地址
   */
  virtual auto alloc(std::size_t size, std::size_t hash_id,
                     const std::string &name, const std::string &file_name,
                     int line) -> void * = 0;
};

} // namespace service
} // namespace kratos
